 /*
 * File:   Linda.cpp
 * Author: JM, Michał Kołodziejski
 * 
 * Created on 11 maj 2010, 20:21
 */

#include "Linda.h"
#include "Server.h"
#include "standardLindaHeaders.h"

//LoggerPtr logger(Logger::getLogger("Linda"));

Linda::Linda(const int aQueueId) {
    iQueueId = msgget(aQueueId, IPC_CREAT | IPC_EXCL | 0600);

    if (iQueueId == -1) {
        iQueueId = msgget(aQueueId, 0600);
        std::stringstream ss;
        ss << "Global queue exists, id: " << iQueueId;
        LOG4CXX_INFO(logger, ss.str());
        //EEXIST
    } else {
        //fork - start server
        std::stringstream ss;
        ss << "Global queue created, id: " << iQueueId;
        LOG4CXX_INFO(logger, ss.str());
        switch (fork()) {
        case -1:
            //parent, but failed
            LOG4CXX_ERROR(logger, "Fork failed");
            break;
        case 0:
            {
                //child
                Server& s = Server::getInstance();
                s.init(iQueueId);
                s.do_it();
                exit(0);
                break;
            }
        default:
            //parent
            break;
        }

    }

    iPrivateQueueId = msgget(IPC_PRIVATE, 0600);
}

Linda::Linda(const Linda& orig) {
    this->iPrivateQueueId = orig.iPrivateQueueId;
    this->iQueueId = orig.iQueueId;
}

Linda::~Linda() {
    LOG4CXX_INFO(logger, "Client closing...");
    if (msgctl(iPrivateQueueId, IPC_RMID, NULL) != 0) {
        //catastrophy
        LOG4CXX_ERROR(logger, "Cannot remove local queue");
    }
}

std::string Linda::read(const std::string aChars, const int aTimeout) throw(LindaReceivingException,
            LindaSendingException, LindaTimeoutException, LindaServerErrorException, LindaMalformedMessageException) {
    LOG4CXX_INFO(logger, "Sending read request...");
    return sendToServerAndWaitForAnswer(2, aChars, aTimeout);
}

std::string Linda::input(const std::string aChars, const int aTimeout) throw(LindaReceivingException,
            LindaSendingException, LindaTimeoutException, LindaServerErrorException, LindaMalformedMessageException) {
    LOG4CXX_INFO(logger, "Sending input request...");
    return sendToServerAndWaitForAnswer(3, aChars, aTimeout);
}

void Linda::output(const std::string aChars) throw(LindaSendingException) {
    LOG4CXX_INFO(logger, "Sending output request...");
    LindaMsg msg;
    int msgSize = sizeof(struct LindaMsg) - sizeof(long);
    msg.type = 1;
    strncpy(msg.msg, aChars.c_str(), MAX_QUEUE_MSG_SIZE-1);
    msg.msg[MAX_QUEUE_MSG_SIZE-1] = '\0';
    if (msgsnd(iQueueId, &msg, msgSize, 0) == -1) {
        LOG4CXX_ERROR(logger, "Error while sending msg to server");
        throw LindaSendingException();
    } else {
        LOG4CXX_INFO(logger, "Sent msg to server");
    }

}

std::string Linda::sendToServerAndWaitForAnswer(const int aType, const std::string aChars, const int aTimeout)
            throw(LindaSendingException, LindaReceivingException, LindaTimeoutException, LindaServerErrorException,
                  LindaMalformedMessageException) {
    int i, res;
    {
        LindaMsg msg;
        int msgSize = sizeof(struct LindaMsg) - sizeof(long);
        msg.type = aType;
        msg.timeout = aTimeout;
        msg.queueId = iPrivateQueueId;
        strncpy(msg.msg, aChars.c_str(), MAX_QUEUE_MSG_SIZE-1);
        msg.msg[MAX_QUEUE_MSG_SIZE-1] = '\0';
        if (msgsnd(iQueueId, &msg, msgSize, 0) == -1) {
            LOG4CXX_ERROR(logger, "Error while sending msg to server");
            throw LindaSendingException();
        } else {
            LOG4CXX_INFO(logger, "Sent msg to server");
        }
    }

    //wait for an answer
    {
        LindaReplyMsg m;
        int msgSize = sizeof(struct LindaReplyMsg) - sizeof(long);
        if (res = msgrcv(iPrivateQueueId, &m, msgSize, 0, 0)) {
            LOG4CXX_INFO(logger, "Received reply");
            if (res == -1) {
                //if(errno == EINTR){break;}//SIGNAL
                LOG4CXX_INFO(logger, "Error while receiving reply, errno: " + boost::lexical_cast<std::string>(errno));
                throw LindaReceivingException();
            }

            switch (m.type) {
            case 1:
                {
                    return std::string(m.msg);
                    break;
                }
            case 2:
                {
                    //timeout
                    throw LindaTimeoutException();
                    break;
                }
            case 3:
                {
                    //error
                    throw LindaServerErrorException();
                    break;
                }
            case 4:
                {
                    //malformed message
                    throw LindaMalformedMessageException();
                    break;
                }
            default:
                //error
                throw LindaServerErrorException();
                break;
            }
        }
    }
}
